Skip to content

채팅 이미지 미리보기 리팩토링#221

Open
0Jaemin0 wants to merge 6 commits into
developfrom
refactor/#219-ChatRoom-Image
Open

채팅 이미지 미리보기 리팩토링#221
0Jaemin0 wants to merge 6 commits into
developfrom
refactor/#219-ChatRoom-Image

Conversation

@0Jaemin0

@0Jaemin0 0Jaemin0 commented Jul 1, 2026

Copy link
Copy Markdown
Member

📌 관련 이슈

✨ PR 세부 내용

채팅 메시지의 이미지 표시와 미리보기 동작을 별도 컴포넌트와 유틸로 분리했습니다.
기존에는 이미지가 단순 <img> 링크로만 렌더링되어 있었기 때문에, 이미지 크기와 미리보기 동작을 메시지 타입별로 일관되게 제어하기 어려웠습니다. 이번 작업에서는 채팅 이미지 표시 책임을 분리하고, 클릭 시 새 창 미리보기를 통일된 방식으로 처리하도록 정리했습니다.

핵심 변경

  • 이미지 미리보기용 openImagePreviewWindow 유틸 추가
  • 채팅 이미지 표시용 ChatImageMessage 컴포넌트 추가
  • 이미지 비율에 따라 표시 크기를 다르게 적용하도록 정리
  • 이미지 로드 완료 전/후 표시 상태를 분리
  • MyMessageBox / OtherMessageBox에서 이미지 렌더링 로직 교체

구조 개선

  • 이미지 미리보기 동작을 메시지 박스에서 분리해 재사용 가능한 유틸로 정리
  • 이미지 렌더링을 텍스트/파일 렌더링과 분리해 메시지 타입별 책임을 분리
  • 가로/세로/정방형 이미지에 대해 고정된 크기 규칙을 적용하도록 개선

효과

  • 채팅 이미지 표시와 미리보기 동작이 한곳에 모여 유지보수가 쉬워짐
  • 메시지 박스 내부의 조건 분기가 줄어들어 구조가 단순해짐
  • 이미지 비율에 따른 표시 흔들림을 줄이고 미리보기 동작을 통일할 수 있음

📸 스크린샷

채팅 이미지

✅ 리뷰 요구사항

  • 이미지 메시지를 클릭했을 때 새 창 미리보기가 정상적으로 열리는지 확인해주세요.
  • 가로/세로/정방형 이미지가 각각 의도한 크기로 표시되는지 확인해주세요.
  • 텍스트 메시지와 파일 메시지 렌더링이 기존 동작과 다르지 않은지 확인해주세요.

Summary by CodeRabbit

  • New Features

    • 채팅의 이미지 메시지가 개선되어, 이미지 미리보기를 새 탭에서 더 깔끔하게 볼 수 있게 되었습니다.
    • 이미지가 가로/세로/정사각형 비율에 맞춰 자연스럽게 표시되며, 로드 완료 후에만 보여집니다.
    • 이미지 메시지가 여러 장일 때도 줄바꿈과 간격이 더 안정적으로 적용됩니다.
  • Bug Fixes

    • 읽음 처리 시 연결 상태가 없을 때 불필요한 전송을 막아 더 안정적으로 동작합니다.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

채팅 메시지 내 이미지 렌더링 방식을 새로운 ChatImageMessage 컴포넌트로 교체했습니다. 이미지 방향(가로/세로/정사각형)에 따른 스타일과 로드 상태 처리를 담당하는 styled-components가 추가되었고, 클릭 시 openImagePreviewWindow 유틸을 통해 새 탭에서 이미지를 미리보기로 여는 기능이 구현되었습니다. MyMessageBox와 OtherMessageBox의 기존 a/img 마크업이 이 컴포넌트로 대체되었으며, ImageWrapper에 flex-wrap이 추가되었습니다. 또한 useChatMessages의 읽음 처리 로직에 stompClient 존재 여부 검사가 추가되었습니다.

Estimated code review effort: 2 (Simple) | ~15 minutes

변경 사항

파일 변경 내용
ChatImageMessage.styled.ts 방향별 크기 및 로드 상태 기반 스타일 신규 추가
ChatImageMessage.tsx 이미지 방향 계산, 클릭 시 미리보기 오픈 컴포넌트 신규 추가
openImagePreviewWindow.ts 새 탭 이미지 미리보기 유틸 신규 추가
MyMessageBox.tsx/.styled.ts ChatImageMessage 적용, ImageWrapper flex-wrap 추가
OtherMessageBox.tsx/.styled.ts ChatImageMessage 적용, 불필요한 img 스타일 블록 제거
useChatMessages.ts stompClient 존재 여부 가드 조건 추가

관련 이슈

  • 없음(제공된 정보 기준)

관련 PR

  • 없음(제공된 정보 기준)

제안 라벨: enhancement, refactor, frontend

제안 리뷰어: 없음(제공된 정보 기준)

한 줄 시(리뷰어의 마음으로)

이미지 하나에도 방향이 있다니, 세상 참 다채롭구나 🐰
가로냐 세로냐, 정사각형이냐 — 토끼는 오늘도 고민한다
새 탭 열어 미리보기, 클릭 한 번에 뿅!
stompClient 없인 못 보내지, 조심조심 가드 추가
flex-wrap 하나로 줄맞춤 완성, 오늘도 리뷰 끝!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed 제목이 채팅 이미지 미리보기 리팩토링이라는 핵심 변경을 간결하고 명확하게 담고 있습니다.
Description check ✅ Passed 필수 섹션인 관련 이슈, PR 세부 내용, 스크린샷, 리뷰 요구사항이 모두 포함되어 있어 템플릿을 잘 따릅니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/#219-ChatRoom-Image

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@0Jaemin0 0Jaemin0 changed the title 채팅 목록 로딩 및 이미지 미리보기 리팩토링 채팅 이미지 미리보기 리팩토링 Jul 1, 2026
@0Jaemin0 0Jaemin0 requested a review from ledraco July 1, 2026 05:03
@0Jaemin0 0Jaemin0 self-assigned this Jul 1, 2026
@0Jaemin0 0Jaemin0 added the 🔨 Refactor 리팩토링 label Jul 1, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
src/components/Chat/ChatImageMessage/ChatImageMessage.tsx (2)

16-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

이벤트 핸들러 네이밍 컨벤션을 맞춰주세요.

updateImageMetaonLoad에 바인딩되는 이벤트 핸들러인데, 컨벤션상 handle* 접두어를 사용해야 합니다. handleImageLoad 등으로 변경해주세요.

♻️ 제안
-	const updateImageMeta = (event: React.SyntheticEvent<HTMLImageElement>) => {
+	const handleImageLoad = (event: React.SyntheticEvent<HTMLImageElement>) => {
 		const { naturalWidth, naturalHeight } = event.currentTarget;
 		...
 	};
 	...
-				onLoad={updateImageMeta}
+				onLoad={handleImageLoad}

As per path instructions, "이벤트 핸들러는 on*/handle* 규칙 준수"를 따라야 합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Chat/ChatImageMessage/ChatImageMessage.tsx` around lines 16 -
24, The onLoad image event handler in ChatImageMessage should follow the handle*
naming convention instead of update*. Rename updateImageMeta to a
handle-prefixed name such as handleImageLoad, and update its usage in
ChatImageMessage so the event handler naming is consistent with the on*/handle*
rule.

Source: Path instructions


13-14: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

이미지 로드 후 카드 크기 변동으로 인한 레이아웃 점프 가능성.

orientation 기본값이 'portrait'(260×325)인데, 로드 완료 후 실제 방향(landscape: 260×174, square: 260×260)으로 갱신되면 카드 높이가 급격히 바뀌면서 채팅 목록에 시각적 점프가 발생할 수 있습니다. width/height에 짧은 트랜지션을 추가하면 체감 흔들림을 줄일 수 있습니다.

 export const ImageLink = styled.a<{...}>`
   ...
   width: ${(props) => IMAGE_CARD_SIZE[props.$orientation].width};
   height: ${(props) => IMAGE_CARD_SIZE[props.$orientation].height};
+  transition: width 0.15s ease, height 0.15s ease;
 `;

Also applies to: 19-21

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Chat/ChatImageMessage/ChatImageMessage.tsx` around lines 13 -
14, The ChatImageMessage card can visually jump when image loading changes the
`orientation` state from the default `'portrait'` to the final value. Update the
sizing behavior in `ChatImageMessage` so the card transitions smoothly between
the placeholder and final dimensions, especially around the `orientation` state
and the rendered width/height. Add a short transition on the relevant
size-related styles in the component so the height change is less abrupt after
`isLoaded` flips and the actual image orientation is known.
src/components/Chat/MyMessageBox/MyMessageBox.styled.ts (1)

47-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

ImageWrapper 스타일이 OtherMessageBox.styled.ts와 완전히 중복됩니다.

동일한 display: flex; flex-wrap: wrap; gap: ... 블록이 MyMessageBox.styled.tsOtherMessageBox.styled.ts에 각각 정의되어 있습니다. 공용 styled 컴포넌트(예: src/components/Chat/common 또는 유사 위치)로 추출해 재사용하면 이후 스타일 변경 시 두 곳을 동시에 수정할 필요가 없어집니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Chat/MyMessageBox/MyMessageBox.styled.ts` around lines 47 -
51, `ImageWrapper` in `MyMessageBox.styled.ts` is duplicated in
`OtherMessageBox.styled.ts`; extract the shared flex-wrap gap styling into a
common styled component in a shared chat location and update both `ImageWrapper`
usages to reuse it. Keep the existing layout behavior the same while removing
the duplicated `styled.div` block.
src/utils/openImagePreviewWindow.ts (1)

6-18: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

noopener 옵션을 window.open에 직접 지정하는 편이 더 안전합니다.

현재는 빈 창을 연 뒤 opener = null로 사후 해제하는 방식인데, window.open 호출 시 세 번째 인자로 'noopener,noreferrer'를 넘기는 것이 더 관용적이고 확실한 방법입니다. 다만 여기서는 외부 URL로 즉시 네비게이션하지 않고 about:blank 상태에서 DOM을 직접 구성하므로 실질적인 tabnabbing 위험은 낮습니다.

♻️ 제안
-	const imageWindow = window.open('', '_blank');
+	const imageWindow = window.open('', '_blank', 'noopener,noreferrer');

 	if (!imageWindow) return;

-	imageWindow.opener = null;
 	imageWindow.document.title = alt;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/openImagePreviewWindow.ts` around lines 6 - 18,
`openImagePreviewWindow` currently nulls `imageWindow.opener` after opening, but
the safer approach is to pass the security flags directly to `window.open`.
Update the `window.open` call in `openImagePreviewWindow` to include
`'noopener,noreferrer'` as the third argument, and remove the manual `opener =
null` handling so the window is created with the intended isolation from the
start.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/Chat/ChatImageMessage/ChatImageMessage.tsx`:
- Around line 27-37: The ImageLink click handler in ChatImageMessage currently
prevents the browser’s default behavior for every click, which blocks
modifier-based new tab actions. Update the onClick logic on S.ImageLink so it
only calls preventDefault() and openImagePreviewWindow({ src, alt }) for a plain
left-click, and immediately returns when a modifier key is pressed so
ctrl/cmd+click keeps the default new-tab behavior.

In `@src/hooks/chatting/useChatMessages.ts`:
- Line 17: The page merge logic in useChatMessages is tracking only the last
page via lastReadMessageIdRef, which can miss already-cached pages and duplicate
the same tail page on updates. Change the ref to track how many pages have
already been processed, and in the message merge path around the pages handling
logic use that count to append only newly received pages while preserving any
real-time prepended messages. Update the relevant merge/initialization flow in
useChatMessages so repeated React Query cache refreshes are stable and
idempotent.
- Around line 37-43: `useChatMessages`의 읽음 처리 로직에서 `stompClient`가 없을 때도
`lastReadMessageIdRef`가 갱신되어 재전송이 막히는 문제가 있습니다. `stompClient?.send`를 호출하는 부분을
`stompClient` 존재 여부를 먼저 확인한 뒤에만 실행하도록 바꾸고, 성공적으로 전송했을 때만
`lastReadMessageIdRef.current`를 `latestMessageId`로 업데이트하세요. `useChatMessages`와
`lastReadMessageIdRef`를 기준으로 해당 흐름을 찾아 수정하면 됩니다.

In `@src/hooks/chatting/useChatScroll.ts`:
- Around line 15-36: useChatScroll keeps isInitialScrollComplete and
isLatestMessageVisible across chatHistory resets, so switching rooms can reuse
stale scroll state. Update the hook’s state handling so these flags are reset
whenever chatHistory becomes null or changes to a new conversation, alongside
the existing scroll logic in useEffect/useLayoutEffect. Make the reset happen in
useChatScroll using the existing state setters and messageEndRef-based flow so
the initial bottom scroll and latest-message visibility are recalculated per
chat room.

---

Nitpick comments:
In `@src/components/Chat/ChatImageMessage/ChatImageMessage.tsx`:
- Around line 16-24: The onLoad image event handler in ChatImageMessage should
follow the handle* naming convention instead of update*. Rename updateImageMeta
to a handle-prefixed name such as handleImageLoad, and update its usage in
ChatImageMessage so the event handler naming is consistent with the on*/handle*
rule.
- Around line 13-14: The ChatImageMessage card can visually jump when image
loading changes the `orientation` state from the default `'portrait'` to the
final value. Update the sizing behavior in `ChatImageMessage` so the card
transitions smoothly between the placeholder and final dimensions, especially
around the `orientation` state and the rendered width/height. Add a short
transition on the relevant size-related styles in the component so the height
change is less abrupt after `isLoaded` flips and the actual image orientation is
known.

In `@src/components/Chat/MyMessageBox/MyMessageBox.styled.ts`:
- Around line 47-51: `ImageWrapper` in `MyMessageBox.styled.ts` is duplicated in
`OtherMessageBox.styled.ts`; extract the shared flex-wrap gap styling into a
common styled component in a shared chat location and update both `ImageWrapper`
usages to reuse it. Keep the existing layout behavior the same while removing
the duplicated `styled.div` block.

In `@src/utils/openImagePreviewWindow.ts`:
- Around line 6-18: `openImagePreviewWindow` currently nulls
`imageWindow.opener` after opening, but the safer approach is to pass the
security flags directly to `window.open`. Update the `window.open` call in
`openImagePreviewWindow` to include `'noopener,noreferrer'` as the third
argument, and remove the manual `opener = null` handling so the window is
created with the intended isolation from the start.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1aee6775-0360-46eb-ae2d-7389c496731a

📥 Commits

Reviewing files that changed from the base of the PR and between 073c130 and 4a2f667.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (16)
  • package.json
  • src/components/Chat/ChatImageMessage/ChatImageMessage.styled.ts
  • src/components/Chat/ChatImageMessage/ChatImageMessage.tsx
  • src/components/Chat/Chatting/Chatting.styled.ts
  • src/components/Chat/Chatting/Chatting.tsx
  • src/components/Chat/MyMessageBox/MyMessageBox.styled.ts
  • src/components/Chat/MyMessageBox/MyMessageBox.tsx
  • src/components/Chat/OtherMessageBox/OtherMessageBox.styled.ts
  • src/components/Chat/OtherMessageBox/OtherMessageBox.tsx
  • src/hooks/chatting/useChatInfiniteScroll.ts
  • src/hooks/chatting/useChatInput.ts
  • src/hooks/chatting/useChatMessages.ts
  • src/hooks/chatting/useChatScroll.ts
  • src/hooks/common/useIntersectionObserver.ts
  • src/hooks/queries/chat/useChatMessageQuery.ts
  • src/utils/openImagePreviewWindow.ts

Comment thread src/components/Chat/ChatImageMessage/ChatImageMessage.tsx
const [chatHistory, setChatHistory] = useState<ChatData | null>(null);
const chatRef = useRef<HTMLDivElement>(null);
const [paginationVersion, setPaginationVersion] = useState(0);
const lastReadMessageIdRef = useRef<number | null>(null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

페이지 병합 기준을 “마지막 페이지”가 아니라 “새로 처리한 페이지”로 추적하세요.

React Query 캐시에 여러 pages가 이미 있으면 Line 50-53은 마지막 페이지만 넣어 최신 페이지를 누락할 수 있고, 같은 페이지 배열이 갱신되면 마지막 페이지를 중복 append할 수 있습니다. 처리한 page count를 ref로 관리하면 실시간으로 prepend된 메시지는 보존하면서 초기 캐시/추가 페이지를 안정적으로 병합할 수 있습니다.

수정 예시
 	const [paginationVersion, setPaginationVersion] = useState(0);
 	const lastReadMessageIdRef = useRef<number | null>(null);
+	const loadedPageCountRef = useRef(0);
@@
 		setChatHistory(null);
 		setPaginationVersion(0);
 		lastReadMessageIdRef.current = null;
+		loadedPageCountRef.current = 0;
 	}, [selectedChat]);
@@
 	useEffect(() => {
 		if (!messagePages) return;
 
+		const nextPages = messagePages.slice(loadedPageCountRef.current);
+		if (nextPages.length === 0) return;
+
+		const nextMessages = nextPages.flatMap((page) => page.chatChannelMessages);
+		loadedPageCountRef.current = messagePages.length;
+
 		setChatHistory((prevChatHistory) => {
-			const lastPageMessages = messagePages[messagePages.length - 1]?.chatChannelMessages ?? [];
-
 			return {
-				chatChannelMessages: [...(prevChatHistory?.chatChannelMessages ?? []), ...lastPageMessages],
+				chatChannelMessages: [...(prevChatHistory?.chatChannelMessages ?? []), ...nextMessages],
 			};
 		});
 		setPaginationVersion((version) => version + 1);
 	}, [messagePages]);

As per path instructions, API 호출, 상태 관리(Zustand, React Query), 컴포넌트 구조가 적절하게 분리되어 있는지 확인해주세요.

Also applies to: 25-29, 46-57

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/chatting/useChatMessages.ts` at line 17, The page merge logic in
useChatMessages is tracking only the last page via lastReadMessageIdRef, which
can miss already-cached pages and duplicate the same tail page on updates.
Change the ref to track how many pages have already been processed, and in the
message merge path around the pages handling logic use that count to append only
newly received pages while preserving any real-time prepended messages. Update
the relevant merge/initialization flow in useChatMessages so repeated React
Query cache refreshes are stable and idempotent.

Source: Path instructions

Comment thread src/hooks/chatting/useChatMessages.ts Outdated
Comment on lines 15 to +36
const [isScrollAtBottom, setIsScrollAtBottom] = useState(false);
const [initialLoad, setInitialLoad] = useState(true);
const [isInitialScrollComplete, setIsInitialScrollComplete] = useState(false);
const [isLatestMessageVisible, setIsLatestMessageVisible] = useState(false);
const messageEndRef = useRef<HTMLInputElement | null>(null);
const messageEndRef = useRef<HTMLDivElement | null>(null);

useEffect(() => {
if (!chatHistory || chatHistory.chatChannelMessages.length === 0) return;

if (isScrollAtBottom) {
messageEndRef.current?.scrollIntoView();
setIsScrollAtBottom(false);
} else if (chatRef.current)
window.scrollTo({ top: chatRef.current.scrollHeight - prevHeight });
}, [chatHistory]);
if (!chatHistory || chatHistory.chatChannelMessages.length === 0 || !isScrollAtBottom) {
return;
}

useEffect(() => {
const observedElement = chatRef.current;
const resizeObserver = new ResizeObserver(() => {
if (initialLoad) {
messageEndRef.current?.scrollIntoView();
setInitialLoad(false);
}
});
messageEndRef.current?.scrollIntoView();
setIsScrollAtBottom(false);
}, [chatHistory, isScrollAtBottom]);

if (observedElement) resizeObserver.observe(observedElement);
useLayoutEffect(() => {
if (isInitialScrollComplete || !chatHistory || chatHistory.chatChannelMessages.length === 0) {
return;
}

return () => {
if (observedElement) resizeObserver.unobserve(observedElement);
};
}, []);
messageEndRef.current?.scrollIntoView();
setIsInitialScrollComplete(true);
}, [chatHistory, isInitialScrollComplete]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

채팅 히스토리 초기화 시 스크롤 상태도 함께 초기화하세요.

useChatMessages는 채팅방 변경 시 chatHistorynull로 초기화하지만, 이 훅의 isInitialScrollComplete/isLatestMessageVisible은 유지됩니다. 다음 채팅방에서 초기 하단 스크롤이 스킵되거나 이전 방의 최신 메시지 버튼 상태가 남을 수 있습니다.

수정 예시
 	useLayoutEffect(() => {
-		if (isInitialScrollComplete || !chatHistory || chatHistory.chatChannelMessages.length === 0) {
+		if (!chatHistory) {
+			setIsScrollAtBottom(false);
+			setIsInitialScrollComplete(false);
+			setIsLatestMessageVisible(false);
+			return;
+		}
+
+		if (isInitialScrollComplete || chatHistory.chatChannelMessages.length === 0) {
 			return;
 		}
 
 		messageEndRef.current?.scrollIntoView();

As per path instructions, 불필요한 상태 관리null/undefined 처리, 에러 처리 등 안정성 관점을 기준으로 확인했습니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [isScrollAtBottom, setIsScrollAtBottom] = useState(false);
const [initialLoad, setInitialLoad] = useState(true);
const [isInitialScrollComplete, setIsInitialScrollComplete] = useState(false);
const [isLatestMessageVisible, setIsLatestMessageVisible] = useState(false);
const messageEndRef = useRef<HTMLInputElement | null>(null);
const messageEndRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (!chatHistory || chatHistory.chatChannelMessages.length === 0) return;
if (isScrollAtBottom) {
messageEndRef.current?.scrollIntoView();
setIsScrollAtBottom(false);
} else if (chatRef.current)
window.scrollTo({ top: chatRef.current.scrollHeight - prevHeight });
}, [chatHistory]);
if (!chatHistory || chatHistory.chatChannelMessages.length === 0 || !isScrollAtBottom) {
return;
}
useEffect(() => {
const observedElement = chatRef.current;
const resizeObserver = new ResizeObserver(() => {
if (initialLoad) {
messageEndRef.current?.scrollIntoView();
setInitialLoad(false);
}
});
messageEndRef.current?.scrollIntoView();
setIsScrollAtBottom(false);
}, [chatHistory, isScrollAtBottom]);
if (observedElement) resizeObserver.observe(observedElement);
useLayoutEffect(() => {
if (isInitialScrollComplete || !chatHistory || chatHistory.chatChannelMessages.length === 0) {
return;
}
return () => {
if (observedElement) resizeObserver.unobserve(observedElement);
};
}, []);
messageEndRef.current?.scrollIntoView();
setIsInitialScrollComplete(true);
}, [chatHistory, isInitialScrollComplete]);
const [isScrollAtBottom, setIsScrollAtBottom] = useState(false);
const [isInitialScrollComplete, setIsInitialScrollComplete] = useState(false);
const [isLatestMessageVisible, setIsLatestMessageVisible] = useState(false);
const messageEndRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (!chatHistory || chatHistory.chatChannelMessages.length === 0 || !isScrollAtBottom) {
return;
}
messageEndRef.current?.scrollIntoView();
setIsScrollAtBottom(false);
}, [chatHistory, isScrollAtBottom]);
useLayoutEffect(() => {
if (!chatHistory) {
setIsScrollAtBottom(false);
setIsInitialScrollComplete(false);
setIsLatestMessageVisible(false);
return;
}
if (isInitialScrollComplete || chatHistory.chatChannelMessages.length === 0) {
return;
}
messageEndRef.current?.scrollIntoView();
setIsInitialScrollComplete(true);
}, [chatHistory, isInitialScrollComplete]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/chatting/useChatScroll.ts` around lines 15 - 36, useChatScroll
keeps isInitialScrollComplete and isLatestMessageVisible across chatHistory
resets, so switching rooms can reuse stale scroll state. Update the hook’s state
handling so these flags are reset whenever chatHistory becomes null or changes
to a new conversation, alongside the existing scroll logic in
useEffect/useLayoutEffect. Make the reset happen in useChatScroll using the
existing state setters and messageEndRef-based flow so the initial bottom scroll
and latest-message visibility are recalculated per chat room.

Source: Path instructions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 Refactor 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant